package com.spring.theory.aop.advice;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJAfterAdvice;
import org.springframework.aop.aspectj.AspectJAfterReturningAdvice;
import org.springframework.aop.aspectj.AspectJAfterThrowingAdvice;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;

/**
 * aop 1-5 高级切面转换为低级切面
 * <p>
 * 1.高等级切面（添加注解后，会在底层转换成低等级切面运行） 继承抽象类AbstractAspectJAdvice
 * - @Before = AspectJMethodBeforeAdvice                      需要转换成环绕通知
 * - @AfterReturning = AspectJAfterReturningAdvice            需要转换成环绕通知
 * <p>
 * - @Around = AspectJAroundAdvice                            已经是MethodInterceptor
 * - @After = AspectJAfterAdvice                              已经是MethodInterceptor
 * - @AfterThrowing = AspectJAfterThrowingAdvice              已经是MethodInterceptor
 * <p>
 * 2.通知会统一成环绕通知 org.aopalliance.intercept.MethodInterceptor; 如果通知类本身实现了 MethodInterceptor 接口,则不用转换
 * - 无论 ProxyFactory 基于哪种方式创建代理，最后都是 MethodInvocation对象 调用advice；多个advisor 嵌套调用就需要一个调用链对象(MethodInvocation对象)
 * - 统一转换为环绕通知体现的是 适配器模式(将一套接口转换成另一套接口)
 * a.对外提供before，afterReturning等
 * b.对内统一都是环绕通知，统一调用 MethodInterceptor ；
 * 如 @before  通过MethodBeforeAdviceAdapter, 将原通知是AspectJMethodBeforeAdvice 转换成 MethodBeforeAdviceInterceptor(继承了MethodInterceptor)
 */
@Slf4j
public class TestAspectTransfer {

    static class TestAspectAnno {
        @Before("execution(* test())")
        public void before() {
            log.info("TestAspectAnno方法 before注解运行");
        }

        @Before("execution(* test())")
        public void before2() {
            log.info("TestAspectAnno方法 before2注解运行");
        }

        @After("execution(* test())")
        public void after() {
            log.info("TestAspectAnno方法 after注解运行");
        }

        @AfterReturning("execution(* test())")
        public void afterReturning() {
            log.info("TestAspectAnno方法 afterReturning注解运行");
        }

        @AfterThrowing("execution(* test())")
        public void afterThrowing(Exception e) {
            log.info("TestAspectAnno方法 afterThrowing注解运行：{}", e.getMessage());
        }

        /**
         * @Around的连接点是 ProceedingJoinPoint
         * 其它四种通知连接点是 JoinPoint
         */
        @Around("execution(* test())")
        public Object around(ProceedingJoinPoint pt) {
            try {
                log.info("TestAspectAnno方法 around注解运行==前");
                return pt.proceed();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            } finally {
                log.info("TestAspectAnno方法 around注解运行==后");
            }
        }

    }

    static class Target {
        public void test() {
            log.info("Target目标的test方法执行");
        }
    }

    public static void main(String[] args) throws Throwable {
        SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(new TestAspectAnno());
        List<Advisor> list = new ArrayList<>();
        /**
         * 1.解析高级切面转换成低级切面类;并放入集合
         */
        for (Method method : TestAspectAnno.class.getMethods()) {
            parseAnnotation(method, factory, list);
        }
        for (Advisor advisor : list) {
            log.info("集合内的advisor:{}", advisor);
        }

        /**
         * 2.统一转换成环绕通知
         */
        Target target = new Target();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        /**
         * 某些通知内部可能需要使用调用链对象。因此需要将调用链对象存放在某一位置，使所有通知都能获取到调用链对象
         * 注：将调用链提前放入当前线程，每一个通知都可以使用到；（准备好MethodInvocation对象）
         */
        proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);

        proxyFactory.addAdvisors(list);
        List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("test"), Target.class);
        for (Object obj : methodInterceptorList) {
            log.info("已转换的环绕通知:{}", obj);
        }

        /**
         * 3.创建并且执行调用链
         * 需要提前在 ProxyFactory 里面指定调用链 MethodInvocation
         */
        Constructor<ReflectiveMethodInvocation> constructor = ReflectiveMethodInvocation.class.getDeclaredConstructor(Object.class, Object.class, Method.class, Object[].class, Class.class, List.class);
        constructor.setAccessible(true);
        ReflectiveMethodInvocation methodInvocation = constructor.newInstance(null, target, Target.class.getMethod("test"), new Object[0], Target.class, methodInterceptorList);
        methodInvocation.proceed();

    }

    private static final Map<Class<? extends Annotation>, Class<?>> map = new HashMap<>();

    private static void parseAnnotation(Method method, SingletonAspectInstanceFactory factory, List<Advisor> list) {
        for (Entry<Class<? extends Annotation>, Class<?>> d : map.entrySet()) {
            Class<? extends Annotation> annotationClazz = d.getKey();
            if (method.isAnnotationPresent(annotationClazz)) {
                try {
                    Annotation annotation = method.getAnnotation(annotationClazz);
                    /**
                     * 1.反射获取切点表达式; 获取注解类型后反射调用值
                     */
                    String pointcutStr = (String) annotation.annotationType().getMethod("value").invoke(annotation);
                    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
                    pointcut.setExpression(pointcutStr);
                    /**
                     * 2.反射创建通知类
                     */
                    Class<?> clazz = d.getValue();
                    Advice advice = (Advice) clazz.getConstructor(Method.class, AspectJExpressionPointcut.class, AspectInstanceFactory.class)
                            .newInstance(method, pointcut, factory);
                    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                    list.add(advisor);
                    //找到后直接返回,跳出循环;一个方法就一个aspect注解
                    return;
                } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException |
                         InstantiationException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    static {
        map.put(Before.class, AspectJMethodBeforeAdvice.class);
        map.put(AfterReturning.class, AspectJAfterReturningAdvice.class);
        map.put(After.class, AspectJAfterAdvice.class);
        map.put(AfterThrowing.class, AspectJAfterThrowingAdvice.class);
        map.put(Around.class, AspectJAroundAdvice.class);
    }

}
